/**
 * 内置组件翻译器
 */
class BuiltInComponentTranslator {
  render(component) {
    throw new Error("render method not implements ... ");
  }
}

class TextTranslator extends BuiltInComponentTranslator {
  render(component) {
    let element = component._renderElement;
    if (!element) {
      // text 元素如果组件已经渲染则直接更新组件属性即可
      element = document.createElement("span");
    }
    element.innerText = component.text;
    return element;
  }
}

class ImageTranslator extends BuiltInComponentTranslator {
  render(component) {
    let element = component._renderElement;
    if (!element) {
      // image 元素如果组件已经渲染则直接更新组件属性即可
      element = document.createElement("img");
    }
    element.src = component.src;
    return element;
  }
}

class BaseContainerTranslator extends BuiltInComponentTranslator {
  render(component) {
    let element = component._renderElement;
    if (!element) {
      // image 元素如果组件已经渲染则直接更新组件属性即可
      element = document.createElement("div");
    }
    component.children.forEach((child, index) => {
      const childElement = child.render();
      element.appendChild(childElement);
    });
    return element;
  }
}

/**
 * 表格渲染转换器
 */
class TableTranslator extends BuiltInComponentTranslator {
  render(component) {
    let element = component._renderElement;
    if (!element) {
      // image 元素如果组件已经渲染则直接更新组件属性即可
      element = document.createElement("table");
    }
    // component.children.forEach((child, index) => {
    //   const childElement = child.render();
    //   element.appendChild(childElement);
    // });
    return element;
  }
}

// `
// <loop source='slot/children' indexName='index' valuenName='child'>
//  <child />
// </loop>
// `
const BuiltInBaseContainerModelXml = `
    <BaseContainer>
      <slots>
        <children type="ComponentModel[]"/>
      </slots>
      <layout>
        <translator type='BaseContainerTranslator'/>
      </layout>
    </BaseContainer>
  `;

// f_ 表示 false_xxxx , 即表示某个属性为 false
const BuiltInTextModelXml = `
    <Text>
      <props>
        <value type="Object"/>
      </props>
      <layout>
        <translator type='TextTranslator'/>
      </layout>
    </Text>
  `;

const BuiltInImageModelXml = `
    <Image>
      <props>
        <src type="Object"/>
      </props>
      <layout>
        <translator type='ImageTranslator'/>
      </layout>
    </Image>
  `;

// 用 inner 定义内联组件 , 布局插槽内部定义的唯一元素视为默认插槽
const BuiltInTableModelXml = `
    <Table>
      <props>
        <data type="Map[String,Object][] | Object[]"/>
      </props>
      <inner>
        <TableColumn>
          <props>
            <grid type="Boolean" />
            <name type="String" requried="true"/>
            <columnName type="String" out="true"/>
            <columnValue type="Object" out="true"/>
          </props>
          <slots>
            <head type="ComponentModel">
              <Text value="{./columnName}"/>
            </head>
            <body type="ComponentModel">
              <Text value="{./columnValue}"/>
            </body>
            <foot type="ComponentModel"/>
          </slots>
        </TableColumn>
        <TableRow>
          <slots>
            <children type="ComponentModel[]"/>
          </slots>
        </TableRow>
      </inner>
      <slots>
        <rows type="TableRow[]" reject="columns" />
        <columns type="TableColumn[]" reject="rows" />
      </slots>
      <layout>
        <translator type='TableTranslator'/>
      </layout>
    </Table>
  `;

const test1 = `
    Table
      Row propA=xxxx propB=yyyy propC="xxxxxxxxxxxxxx" 
        #普通多层嵌套数据
        Colum propD=1234 propE=5687 
        Colum propD=1234 propE=5687
        Colum propD=1234 propE=56874
        Colum propD=3333 propE=55555
          #propF="
          这是一段可以支持多行的字符串,
          这是第二行
          这是第三行
          "
    #表格型数据
    [UserList][name|age|level|ip]
      "张三" | 30 | A | 192.168.1.1 
      "李四" | 30 | A | 192.168.1.1
      "李四" |    | A | 192.168.1.1
      "王五" |Null| A | 192.168.1.1
      "王五" |Null| A | ""
    UserList2
      User name="张三" age=5 level=B ip=120.0.0.1
      # 多行数据
      User name="张三" age=5 level=B ip=120.0.0.1 
          name="张三" age=5 level=B ip=120.0.0.1name="张三" age=5 level=B ip=120.0.0.1name="张三" age=5 level=B ip=120.0.0.1name="张三" age=5 level=B ip=120.0.0.1name="张三" age=5 level=B ip=120.0.0.1name="张三" age=5 level=B ip=120.0.0.1name="张三" age=5 level=B ip=120.0.0.1name="张三" age=5 level=B ip=120.0.0.1name="张三" age=5 level=B ip=120.0.0.1name="张三" age=5 level=B ip=120.0.0.1name="张三" age=5 level=B ip=120.0.0.1
      /
      # 匿名数据
      - name="张三" age=5 level=B ip=120.0.0.1 
    [datas]
      -

      /

`;

/**
 * 组件模型
 */
class ComponentModel {}

/**
 * 组件模型 构建器
 */
class ComponentModelBuilder {
  constructor() {
    this._componentModelIndex = {};

    this._xmlDomparser = new DOMParser();

    // 内置模型
    const builtInComponentModelList = [
      BuiltInBaseContainerModelXml,
      BuiltInTextModelXml,
      BuiltInImageModelXml,
      BuiltInTableModelXml,
    ];

    // 构建内置模型
    builtInComponentModelList.forEach((model, index) => {
      this.buildModelFromDocument(model);
    });
  }

  /**
   * 构建模型
   * @param {*} model
   * @returns
   */
  buildModelFromDocument(model) {
    if (typeof model === "string") {
      let xml = model.trim();
      if (!xml.startsWith("<") || !xml.endsWith(">")) {
        throw new Error("the model data is not a valid xml of document ");
      }
      const xmlDocument = this._xmlDomparser.parseFromString(xml, "text/xml");
      return this.buildModelFromDocumentFromXml(xmlDocument.firstChild);
    }
  }

  /**
   * 从xml 文档构建模型
   * @param {*} xmlDocument
   */
  buildModelFromDocumentFromXml(root) {
    const componentName = root.nodeName;
    console.info("model name is ", componentName);
    if (this._componentModelIndex[componentName]) {
      return this._componentModelIndex[componentName];
    }

    const componentModel = {
      layout: [],
    };
    componentModel.name = componentName;

    root.querySelectorAll(":scope > inner > * ").forEach((inner) => {
      if (!componentModel.inner) {
        componentModel.inner = [];
      }
      const innerDefine = this.buildModelFromDocumentFromXml(inner);
      componentModel.inner.push(innerDefine);
    });

    root.querySelectorAll(":scope > slots > *").forEach((slot) => {
      if (!componentModel.slots) {
        componentModel.slots = [];
      }
      const slotDefine = {
        _name: slot.nodeName,
        type: slot.attributes.getNamedItem("type").value,
      };

      const tempSlot = this._buildModelLayoutFromXml(slot);
      if (slot.firstChild) {
        slot["default"] = this._buildModelLayoutFromXml(slot.firstChild);
      }
      componentModel.slots.push(slotDefine);
    });

    root.querySelectorAll(":scope > props > * ").forEach((prop) => {
      if (!componentModel.props) {
        componentModel.props = [];
      }
      const propDefine = {
        _name: prop.nodeName,
        type: prop.attributes.getNamedItem("type").value,
      };
      componentModel.props.push(propDefine);
    });

    root.querySelectorAll(":scope > layout > *").forEach((layout) => {
      if (!componentModel.layout) {
        componentModel.layout = [];
      }
      const layoutDefine = this._buildModelLayoutFromXml(layout);
      componentModel.layout.push(layoutDefine);
    });

    root.querySelectorAll(":scope > datas > *").forEach((data) => {
      if (!componentModel.datas) {
        componentModel.datas = {};
      }
      const id = data.getAttribute("id");
      const label = data.getAttribute("label");
      const dataDefine = this._buildModelDatasFromXml(data);
      dataDefine["__id"] = id;
      dataDefine["__label"] = label;
      componentModel.datas[id] = dataDefine;
    });

    this._componentModelIndex[componentName] = componentModel;
    return componentModel;
  }

  _buildModelLayoutFromXml(layout) {
    const layoutDefine = {
      _layout: layout.nodeName,
    };

    const attributes = layout.attributes;
    if (attributes && attributes.length) {
      for (let i = 0; i < attributes.length; i++) {
        const attribute = attributes[i];
        if (!layoutDefine._props) {
          layoutDefine._props = {};
        }
        layoutDefine._props[attribute.name] = attribute.value;
      }
    }

    const children = layout.children;
    if (children && children.length) {
      layoutDefine._content = [];
      for (let i = 0; i < children.length; i++) {
        const child = children[i];
        const childLayoutDefine = this._buildModelLayoutFromXml(child);
        layoutDefine._content.push(childLayoutDefine);
      }
    }
    return layoutDefine;
  }

  /**
   * 解析data数据
   * @param {*} data
   * @returns
   */
  _buildModelDatasFromXml(data) {
    const obj = {};
    // If the node has child nodes, process them
    if (!data.childNodes.length > 0) {
      return obj;
    }
    for (let i = 0; i < data.childNodes.length; i++) {
      const prop = data.childNodes[i];
      // Handle different types of structures
      if (prop.nodeName !== "prop") {
        continue;
      }
      const type = prop.getAttribute("type");
      const propName = prop.getAttribute("name");
      if (type === "Map") {
        obj[propName] = this._buildModelDatasFromXml(prop);
      } else if (type === "ARRAY") {
        if (!Array.isArray(obj[propName])) {
          obj[propName] = [];
        }
        for (let i = 0; i < prop.childNodes.length; i++) {
          let item = prop.childNodes[i];
          if (item.nodeName !== "item") {
            continue;
          }
          obj[propName].push(this._buildModelDatasFromXml(item));
        }
      } else {
        obj[propName] = prop.getAttribute("value");
      }
    }
    return obj;
  }
}

/**
 * 组件 实例
 */
class ComponentInstance {}

/**
 * 实例构建器
 */
class ComponentInstanceBuilder {
  buildInstanceFromModel(modelDocument, data) {
    let temp = { layout: modelDocument.layout, slots: modelDocument.slots };
    const instance = this._deepCopy(temp);
    instance.props = this._assignProps(modelDocument.props, data);
    return instance;
  }

  _assignProps(propsDefine, data) {
    const props = {};
    propsDefine.forEach((propDefine) => {
      let propName = propDefine["_name"];
      if (typeof data[propName] !== "undefined") {
        props[propName] = this._deepCopy(data[propName]);
      }
    });
    return props;
  }

  _deepCopy(obj) {
    if (obj === null || typeof obj !== "object") {
      return obj;
    }
    let copy = Array.isArray(obj) ? [] : {};
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        copy[key] = this._deepCopy(obj[key]);
      }
    }
    return copy;
  }
}

/**
 * 渲染器 , 用于渲染创建后的实力
 */
class ComponentInstanceRender {
  render(instance, element) {
    const fragment = new DocumentFragment();

    element.appendChild(fragment);
  }
}

/**
 * 编辑器,提供编辑入口
 */
class Editor {
  constructor() {
    this.modelBuilder = new ComponentModelBuilder();
    this.instanceBuilder = new ComponentInstanceBuilder();
    this.render = new ComponentInstanceRender();
    this.rootModel = null;
    this.rootInstance = null;

    this._targetData = null;
    this._renderElement = null;
  }
  load(modelDocument) {
    this.rootModel = this.modelBuilder.buildModelFromDocument(modelDocument);
  }

  /**
   * 切换演示数据
   * @param {*} id
   */
  switchDemoData(id) {
    if (this.rootModel && this.rootModel.datas && this.rootModel.datas[id]) {
      const target = this.rootModel.datas[id];
      if (target !== this._targetData) {
        this._targetData = target;
        this._render();
      }
    }
  }

  bind(element) {
    if (!element) {
      throw new Error("element can not be null");
    }
    if (!this.rootModel) {
      throw new Error("the model is not loaded ");
    }
    this.rootInstance = this.instanceBuilder.buildInstanceFromModel(
      this.rootModel,
      this._targetData
    );

    console.log(
      "model instance = ",
      JSON.stringify(this.rootInstance, null, 2)
    );
    this._renderElement = element;
    this._render();
  }

  _render() {
    if (!this.rootInstance || !this._renderElement) {
      return;
    }
    this.render.render(this.rootInstance, this._renderElement);
  }
}

// 历史数据模板
const HistoryTemplate = {
  loginTime: "",
  logoutTime: "",
  ip: "",
};

// 如果元素只需要默布局槽可作为匿名使用 #rows 表示绑定到 rows槽中
// loop #rows component="TableRow"  中如果 rows 插槽的类型固定则 , component="TableRow"  可以省略
const UserProfileModelXml = `
    <UserProfile>
      <props>
        <nickName type="String"  />
        <id type="String"  />
        <age type="String"  />
        <avator type="String"  />
        <properties type="Map[String,Object]"  />
        <history type="History[]"  />
      </props>
      <layout>
        <Text value="{/nickName}" name="nickNameLabel" />
        <BaseContainer>
          <Text value="{/age}" name="ageLabel" />
          <BaseContainer>
            <slot ref="children">
              <Image src="{avator}" name="avatorImage" />
            </slot>
          </BaseContainer>
          <Table >
            <slot ref="rows">
              <loop source="{properties}" indexName="propName" valueName="propValue" >
                <Text value="{/propName}" name="propNameLabel" />
                <Text value="{/propValue}" name="propValueLabel" />
              </loop>
            </slot>
          </Table>
          <Table data="{/history}">
            <slot ref="columns">
              <TableColumn name="loginTime"/>
              <TableColumn name="logoutTime"/>
              <TableColumn name="ip"/>
              <TableColumn>
                <Image src="{avator}" name="avatorImage" />
              </TableColumn>
            </slot>
          </Table>
        </BaseContainer>
      </layout>
      <datas>
        <data id="001" label="普通状态" >
          <prop name="nickName" value="张三"  />
          <prop name="id" value="001"  />
          <prop name="age" value="18"  />
          <prop name="avator" value="src1"  />
          <prop name="properties" type="Map">
            <prop name="propA" value="12345646" />
            <prop name="propB" value="323423432" />
            <prop name="propC" value="23423512351" />
            <prop name="propD" value="23534676235" />
          </prop>
          <prop name="history" type="ARRAY">
            <item>
              <prop name="loginTime" value="2024" />
              <prop name="logoutTime" value="2024" />
              <prop name="ip" value="122.123.123.2222" />
            </item>
            <item>
              <prop name="loginTime" value="2024" />
              <prop name="logoutTime" value="2024" />
              <prop name="ip" value="122.123.123.111" />
            </item>
            <item>
              <prop name="loginTime" value="2024" />
              <prop name="logoutTime" value="2024" />
              <prop name="ip" value="122.123.123.121" />
            </item>
            <item>
              <prop name="loginTime" value="2024" />
              <prop name="logoutTime" value="2024" />
              <prop name="ip" value="122.123.123.110" />
            </item>
          </prop>
        </data>
         <data id="002" label="普通状态2" >
          <prop name="nickName" value="张三"  />
          <prop name="id" value="001"  />
          <prop name="age" value="18"  />
          <prop name="avator" value="src1"  />
          <prop name="properties" type="Map">
            <prop name="propA" value="12345646" />
            <prop name="propB" value="323423432" />
            <prop name="propC" value="23423512351" />
            <prop name="propD" value="23534676235" />
          </prop>
          <prop name="history" type="ARRAY">
            <item>
              <prop name="loginTime" value="2024" />
              <prop name="logoutTime" value="2024" />
              <prop name="ip" value="122.123.123.2222" />
            </item>
            <item>
              <prop name="loginTime" value="2024" />
              <prop name="logoutTime" value="2024" />
              <prop name="ip" value="122.123.123.111" />
            </item>
            <item>
              <prop name="loginTime" value="2024" />
              <prop name="logoutTime" value="2024" />
              <prop name="ip" value="122.123.123.121" />
            </item>
            <item>
              <prop name="loginTime" value="2024" />
              <prop name="logoutTime" value="2024" />
              <prop name="ip" value="122.123.123.110" />
            </item>
          </prop>
        </data>
      </datas>
    </UserProfile>
  `;

const editor = new Editor();
editor.load(UserProfileModelXml);
editor.switchDemoData("001");

let appElement = document.getElementById("app");
editor.bind(appElement);
